//
// Created by xuwj on 2021/11/8.
//

#include "ImageProcessing/SmallTools.h"
#include "unistd.h"

using namespace cv;
using namespace std;

namespace CV_Assistant{
namespace ImageProcessing{

    void Merge(const cv::Mat &src_a, const cv::Mat &src_b, cv::Mat &dst, int flag) {
        int a_cols = src_a.cols;
        int a_rows = src_a.rows;
        int b_cols = src_b.cols;
        int b_rows = src_b.rows;
        int origin_type = src_a.type();

        switch (flag) {
            case WithRight:
            case WithLeft:
                if(a_rows == b_rows) {
                    dst = Mat(a_rows,a_cols + b_cols,origin_type,Scalar(0));
                    MergeEqual(src_a,src_b,dst,flag);
                }else if (a_rows > b_rows){
                    Mat tmp_b;
                    double fy = static_cast<double>(a_rows)/static_cast<double >(b_rows);
                    resize(src_b,tmp_b,Size((double)b_cols*fy,a_rows),0,0,INTER_LINEAR);
                    dst = Mat(a_rows,a_cols + tmp_b.cols,origin_type,Scalar(0));
                    MergeEqual(src_a,tmp_b,dst,flag);
                }else if (a_rows < b_rows){
                    Mat tmp_a;
                    double fy = static_cast<double>(b_rows)/static_cast<double >(a_rows);
                    resize(src_a,tmp_a,Size((double)a_cols*fy,b_rows),0,0,INTER_LINEAR);
                    dst = Mat(b_rows,tmp_a.cols + b_cols,origin_type,Scalar(0));
                    MergeEqual(tmp_a,src_b,dst,flag);
                }
                break;

            case WithBottom:
            case WithTop:
                if(a_cols == b_cols){
                    dst = Mat(a_rows+b_rows,a_cols,origin_type,Scalar(0));
                    MergeEqual(src_a,src_b,dst,flag);
                }else if(a_cols > b_cols){
                    Mat tmp_b;
                    double fx = static_cast<double>(a_cols)/static_cast<double>(b_cols);
                    resize(src_b,tmp_b,Size(a_cols,(double)b_rows*fx),0,0,INTER_LINEAR);
                    dst = Mat(a_rows+tmp_b.rows,a_cols,origin_type,Scalar(0));
                    MergeEqual(src_a,tmp_b,dst,flag);
                } else if(a_cols < b_cols){
                    Mat tmp_a;
                    double fx = static_cast<double>(b_cols)/static_cast<double>(a_cols);
                    resize(src_a,tmp_a,Size(b_cols,(double)a_rows*fx),0,0,INTER_LINEAR);
                    dst = Mat(tmp_a.rows+b_rows,b_cols,origin_type,Scalar(0));
                    MergeEqual(tmp_a,src_b,dst,flag);
                }
                break;
        }
        
        
    }

    void MergeEqual(const cv::Mat &src_a, const cv::Mat &src_b, cv::Mat &dst,
                                                   int flag) {
        using namespace std;
        int cols_a = src_a.cols;
        int rows_a = src_a.rows;
        int cols_b = src_b.cols;
        int rows_b = src_b.rows;
        int channels_ = src_a.channels();

        switch (flag) {
            //src_b will be at src_a's right
            case WithRight:
                for (int i = 0, iend = dst.rows; i < iend; ++i) {
                    for (int j = 0, jend = dst.cols; j < jend; ++j) {
                        if(j < cols_a) {
                            if(channels_ == 3){
                                dst.ptr(i)[3*j] = src_a.ptr(i)[3*j];
                                dst.ptr(i)[3*j+1] = src_a.ptr(i)[3*j+1];
                                dst.ptr(i)[3*j+2] = src_a.ptr(i)[3*j+2];
                            }else if(channels_ == 1){
                                dst.ptr(i)[j] = src_a.ptr(i)[j];
                            }
                        }
                        else {
                            if(channels_ == 3){
                                dst.ptr(i)[3*j] = src_b.ptr(i)[3*(j-cols_a)];
                                dst.ptr(i)[3*j+1] = src_b.ptr(i)[3*(j-cols_a)+1];
                                dst.ptr(i)[3*j+2] = src_b.ptr(i)[3*(j-cols_a)+2];
                            }else if(channels_ == 1){
                                dst.ptr(i)[j] = src_b.ptr(i)[j-cols_a];
                            }
                        }
                    }

                }
                break;
            //src_b will be at src_a's left
            case WithLeft:
                for (int i = 0, iend = dst.rows; i < iend; ++i) {
                    for (int j = 0, jend = dst.cols; j < jend; ++j) {
                        if(j < cols_b) {
                            if(channels_ == 3){
                                dst.ptr(i)[3*j] = src_b.ptr(i)[3*j];
                                dst.ptr(i)[3*j+1] = src_b.ptr(i)[3*j+1];
                                dst.ptr(i)[3*j+2] = src_b.ptr(i)[3*j+2];
                            }else if(channels_ == 1){
                                dst.ptr(i)[j] = src_b.ptr(i)[j];
                            }
                        }
                        else {
                            if(channels_ == 3){
                                dst.ptr(i)[3*j] = src_a.ptr(i)[3*(j-cols_b)];
                                dst.ptr(i)[3*j+1] = src_a.ptr(i)[3*(j-cols_b)+1];
                                dst.ptr(i)[3*j+2] = src_a.ptr(i)[3*(j-cols_b)+2];
                            }else if(channels_ == 1){
                                dst.ptr(i)[j] = src_a.ptr(i)[j-cols_b];
                            }
                        }
                    }

                }
                break;

            case WithBottom:
                for (int i = 0, iend = dst.rows; i < iend; ++i) {
                    for (int j = 0, jend = dst.cols; j < jend; ++j) {
                        if(i < rows_a) {
                            if(channels_ == 3){
                                dst.ptr(i)[3*j] = src_a.ptr(i)[3*j];
                                dst.ptr(i)[3*j+1] = src_a.ptr(i)[3*j+1];
                                dst.ptr(i)[3*j+2] = src_a.ptr(i)[3*j+2];
                            }else if(channels_ == 1){
                                dst.ptr(i)[j] = src_a.ptr(i)[j];
                            }
                        }
                        else {
                            if(channels_ == 3){
                                dst.ptr(i)[3*j] = src_b.ptr(i-rows_a)[3*j];
                                dst.ptr(i)[3*j+1] = src_b.ptr(i-rows_a)[3*j+1];
                                dst.ptr(i)[3*j+2] = src_b.ptr(i-rows_a)[3*j+2];
                            }else if(channels_ == 1){
                                dst.ptr(i)[j] = src_b.ptr(i-rows_a)[j];
                            }
                        }
                    }

                }
                break;

            case WithTop:
                for (int i = 0, iend = dst.rows; i < iend; ++i) {
                    for (int j = 0, jend = dst.cols; j < jend; ++j) {
                        if(i < rows_b) {
                            if(channels_ == 3){
                                dst.ptr(i)[3*j] = src_b.ptr(i)[3*j];
                                dst.ptr(i)[3*j+1] = src_b.ptr(i)[3*j+1];
                                dst.ptr(i)[3*j+2] = src_b.ptr(i)[3*j+2];
                            }else if(channels_ == 1){
                                dst.ptr(i)[j] = src_b.ptr(i)[j];
                            }
                        }
                        else {
                            if(channels_ == 3){
                                dst.ptr(i)[3*j] = src_a.ptr(i-rows_b)[3*j];
                                dst.ptr(i)[3*j+1] = src_a.ptr(i-rows_b)[3*j+1];
                                dst.ptr(i)[3*j+2] = src_a.ptr(i-rows_b)[3*j+2];
                            }else if(channels_ == 1){
                                dst.ptr(i)[j] = src_a.ptr(i-rows_b)[j];
                            }
                        }
                    }

                }
                break;
        }


    }

    void RotateToXY(const cv::Mat &src, cv::Mat &dst,
                    const double* CannyParams, const double* HoughParams,
                    const int center_driftX , const int center_driftY){
        if(src.empty()){
            cout<<"Error, the input image for RotateToXY() is empty.\n";
            return;
        }

        vector<cv::Vec4i> vLines;
        cv::Mat img_canny;
        vector<double> angles;
        vector<int> sum_of_angles;
        cv::Point center(src.cols/2,src.rows/2);

        cv::Canny(src,img_canny,CannyParams[0],CannyParams[1],3);
//        cv::imshow("canny",img_canny);
//        cv::waitKey(0);
        cv::HoughLinesP(img_canny,vLines,HoughParams[0],HoughParams[1],
                        (int)HoughParams[2],HoughParams[3],HoughParams[4]);

        cout<<vLines.size()<<endl;
        for (size_t i = 0, iend = vLines.size(); i < iend; ++i) {
            cv::Vec4i lpoint = vLines[i];
            double Lx_dis = lpoint[2] - lpoint[0];
            double Ly_dis = lpoint[3] - lpoint[1];
            double theta = atan2(Ly_dis,Lx_dis);
            theta = theta/CV_PI *180 ;

            if(angles.empty()){
                angles.push_back(theta);
                sum_of_angles.push_back(1);
            }else{
                for (size_t j = 0; j < angles.size(); ++j) {
                    double theta_dis = fabs(angles[i]-theta);
                    if(theta_dis > 3.0){
                        angles.push_back(theta);
                        sum_of_angles.push_back(1);
                    }else{
                        double angle_avg = (theta + angles[i]*sum_of_angles[i]) / (sum_of_angles[i]+1);
                        angles[i] = angle_avg;
                        sum_of_angles[i] += 1;
                    }
                }
            }
        }

        int max_sum = 0, most_index = 0;
        for (int i = 0, iend = angles.size(); i < iend; ++i) {
            if(max_sum < sum_of_angles[i]){
                max_sum = sum_of_angles[i];
                most_index = i;
            }
        }

        double most_angle = angles[most_index];
        if(most_angle > -90.0 && most_angle < -45.0)
            most_angle = most_angle + 90.0;
        else if(most_angle > 45.0 && most_angle < 90.0)
            most_angle = most_angle - 90.0;

        cout<<"The angle for rotating(from most lines) is "<<most_angle<<endl;

        if(fabs(most_angle) < 0.7) {
            most_angle = 0;
            return;
        }

        cv::Point center_R(center.x + center_driftX, center.y + center_driftY);
        cv::Size dst_size(src.cols , src.rows );
        cv::Mat rotate_matrix = cv::getRotationMatrix2D(center_R,most_angle,1);
        cv::warpAffine(src,dst,rotate_matrix,dst_size,
                       CV_INTER_CUBIC,BORDER_REPLICATE);
        cv::imshow("rotated",dst);
        cv::waitKey(0);

    }

/** AREA-START CUT-OFF LINE HERE--------------------------------------------------
 * Functions related to class ImageOnMouse
 */

    int ImageOnMouse::font_type = cv::FONT_HERSHEY_DUPLEX;
    double ImageOnMouse::font_scale = 2.0;
    int ImageOnMouse::font_thickness = 2.0;
    cv::Scalar ImageOnMouse::font_color = cv::Scalar(255,0,0);

    /**
     * 显示鼠标点的像素在图像坐标系的坐标
     */
    void CB_PointCoordinate(int event, int x, int y, int flags, void *userdata) {
        ImageOnMouse* pImageOnMouse = static_cast<ImageOnMouse*>(userdata);
        if(event == EVENT_LBUTTONDOWN){
            cv::Mat temp = pImageOnMouse->image_.clone();
            string text = "(" + to_string(x) + " , " + to_string(y) + ")";
            cv::putText(temp,text,Point(0,temp.rows-50),ImageOnMouse::font_type,
                        ImageOnMouse::font_scale,ImageOnMouse::font_color,ImageOnMouse::font_thickness);
            imshow(pImageOnMouse->GetWinName(),temp);
        }
    }

    /**
     * 改变框选的像素的值
     */
    void CB_ChangeAreaPixelValue(int event, int x, int y, int flags, void *userdata){

        ImageOnMouse* pImageOnMouse = static_cast<ImageOnMouse*>(userdata);
        if(event == EVENT_LBUTTONDOWN)
        {
            cv::Mat temp = pImageOnMouse->image_.clone();
            string text = "(" + to_string(x) + " , " + to_string(y) + ")";
            cv::putText(temp,text,Point(0,temp.rows-50),ImageOnMouse::font_type,
                        ImageOnMouse::font_scale,ImageOnMouse::font_color,ImageOnMouse::font_thickness);
            imshow(pImageOnMouse->GetWinName(),temp);

            pImageOnMouse->rect_.left_top_ = cv::Point2i(x,y);
            pImageOnMouse->select_flag_ = true;
        }
        else if(pImageOnMouse->select_flag_ && event == EVENT_MOUSEMOVE)
        {
            cv::Mat temp = pImageOnMouse->image_.clone();
            cv::rectangle(temp, pImageOnMouse->rect_.left_top_,Point(x,y),Scalar(0,0,200),2);
            imshow(pImageOnMouse->GetWinName(),temp);
        }
        else if(pImageOnMouse->select_flag_ && event == EVENT_LBUTTONUP)
        {
            pImageOnMouse->rect_.right_bottom_ = Point2i (x,y);
            cv::Mat temp = pImageOnMouse->image_.clone();
            cv::rectangle(temp, pImageOnMouse->rect_.left_top_, pImageOnMouse->rect_.right_bottom_, Scalar(0,0,200),2);
            imshow(pImageOnMouse->GetWinName(),temp);

            cout<<"\t Change all pixels in this area to a selected value?\n "<<
                  "\t Input the value (default as 0), if it's negative or NaN, it won't be changed.\n";
            int value;
            cin >> value;

            if(value >= 0){
                int lt_x = min(pImageOnMouse->rect_.left_top_.x, pImageOnMouse->rect_.right_bottom_.x);
                int lt_y = max(pImageOnMouse->rect_.left_top_.y, pImageOnMouse->rect_.right_bottom_.y);
                int br_x = max(pImageOnMouse->rect_.left_top_.x, pImageOnMouse->rect_.right_bottom_.x);
                int br_y = min(pImageOnMouse->rect_.left_top_.y, pImageOnMouse->rect_.right_bottom_.y);
                cout<<lt_x<<"\t"<<br_x<<"\t"<<br_y<<"\t"<<lt_y<<"\n";
                for(int i = lt_x; i <= br_x; ++i){
                    for(int j = br_y; j <= lt_y; ++j){
                        pImageOnMouse->image_.at<uchar>(j,i) = value;
                    }
                }
            }

            cout<< "\tDone.\n";
            cv::imshow(pImageOnMouse->GetWinName(), pImageOnMouse->image_);
            pImageOnMouse->select_flag_ = false;

            usleep(1000000);
        }
    }


    ImageOnMouse::ImageOnMouse(const std::string win_name) :
    select_flag_(false),win_name_(win_name){
        cv::namedWindow(win_name_,WINDOW_AUTOSIZE);
    }

    ImageOnMouse::ImageOnMouse(const std::string win_name, const cv::Mat &img) :
    select_flag_(false),win_name_(win_name),image_(img){
        cv::namedWindow(win_name_,WINDOW_AUTOSIZE);
    }

    void ImageOnMouse::setCB_PointCoordinate() {
        cv::setMouseCallback(win_name_, &CB_PointCoordinate,this);
        if(!image_.empty()) {
            cout << "image's width: " << image_.cols<<"\t"
                 << "image's height: "<< image_.rows<<endl;
        }
    }

    void ImageOnMouse::setCB_ChangeAreaPixelValue() {
        cv::setMouseCallback(win_name_, &CB_ChangeAreaPixelValue,this);
        if(!image_.empty()) {
            cout << "image's width: " << image_.cols<<"\t"
                 << "image's height: "<< image_.rows<<endl;
        }
    }

    bool ImageOnMouse::showImage() {
        if (!image_.empty()) {
            cv::imshow(win_name_, image_);
            cv::waitKey(0);
            return true;
        }else{
            cout<<"Error, no image input to show.\n";
            return false;
        }
    }

    bool ImageOnMouse::showImage(cv::Mat &img) {
        cv::imshow(win_name_,img);
        cv::waitKey(0);
    }


/**
 * AREA-END CUT-OFF LINE HERE--------------------------------------------------
 */













//namespace ImageProcessing
}
//namespace CV_Assistant
}